﻿// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. 
// See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Actors;
using System.Threading;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Collections.Concurrent;
using System.Cloud.Collections;

namespace ActorStressTests
{
    [Serializable]
    public class Order
    {
        public Order(String itemName, int quantity)
        {
            Item = itemName;
            Quantity = quantity;
            OrderDate = DateTimeOffset.Now;
            OrderId = Guid.NewGuid();
        }

        public String Item { get; set; }
        public int Quantity { get; set; }
        public DateTimeOffset OrderDate { get; set; }
        public Guid OrderId { get; set; }

        public override bool Equals(object obj)
        {
            Order that = obj as Order;
            if (that == null)
                return false;
            return Item == that.Item && Quantity == that.Quantity && OrderDate == that.OrderDate && OrderId.Equals(that.OrderId);
        }

        public override int GetHashCode()
        {
            return Item.GetHashCode() ^ Quantity;
        }

        public override string ToString()
        {
            return String.Format("{0} x {1}", Item, Quantity);
        }
    }

    [Serializable]
    public class Customer
    {
        public Customer(String firstName, String lastName, String city, String country)
        {
            FirstName = firstName;
            LastName = lastName;
            City = city;
            Country = country;
        }

        public override bool Equals(object obj)
        {
            Customer that = obj as Customer;
            if (that == null)
                return false;
            return FirstName == that.FirstName && LastName == that.LastName && City == that.City && Country == that.Country;
        }

        public override int GetHashCode()
        {
            return FirstName.GetHashCode();
        }

        public override string ToString()
        {
            return String.Format("{0} {1}", FirstName, LastName);
        }

        public String FirstName { get; set; }
        public String LastName { get; set; }
        public String City { get; set; }
        public String Country { get; set; }
    }

    static class DictionaryTests
    {
        #region CloudStringDictionary Tests

        public static void StringDictionaryTest(String fabricAddress, bool useGateway)
        {
            Console.WriteLine("Starting CloudStringDictionary<int> test...");
            CloudStringDictionary<int> stringIntMap = new CloudStringDictionary<int>(fabricAddress, "String to Int map", useGateway);
            if (stringIntMap.Count > 0)
            {
                stringIntMap.Clear();
                if (stringIntMap.Count != 0)
                    throw new Exception(String.Format("After calling Clear on an empty dictionary, Count should have been 0, but got: {0}", stringIntMap.Count));
            }
            int num = 5;
            List<String> keys = new List<String>(num);
            List<int> values = new List<int>(num);
            for (int i = 0; i < num; i++)
            {
                keys.Add("key" + i.ToString());
                values.Add(i);
            }
            Task task = stringIntMap.SetValuesAsync(keys, values);
            task.Wait();

            int count;
            if ((count = stringIntMap.CountAsync.Result) != num)
                throw new Exception(String.Format("After setting items, CountAsync returned wrong value!  Got: {0}  Expected: {1}", count, num));
            if ((count = stringIntMap.Count) != num)
                throw new Exception(String.Format("After setting items, Count returned wrong value!  Got: {0}  Expected: {1}", count, num));

            for (int i = 0; i < num; i++)
            {
                int v;
                if ((v = stringIntMap[keys[i]]) != i)
                    throw new Exception(String.Format("After setting items, the default indexed property returned the wrong value for a key!  Key: {0}  Value: {1}  Expected: {2}", keys[i], v, i));
                if ((v = stringIntMap.GetValueAsync(keys[i]).Result) != i)
                    throw new Exception(String.Format("After setting items, GetValueAsync returned the wrong value for a key!  Key: {0}  Value: {1}  Expected: {2}", keys[i], v, i));

                if (!stringIntMap.ContainsKey(keys[i]))
                    throw new Exception(String.Format("ContainsKey failed for key {0}", keys[i]));
                if (!stringIntMap.ContainsKeyAsync(keys[i]).Result)
                    throw new Exception(String.Format("ContainsKeyAsync failed for key {0}", keys[i]));

                // Set all the values to negative now
                stringIntMap[keys[i]] = -values[i];
            }

            // Set a few items in various ways.
            stringIntMap[keys[0]] = Int32.MinValue;
            stringIntMap.SetValueAsync(keys[0], values[0]).Wait();
            stringIntMap.SetValueAsync(keys[0], -stringIntMap.GetValueAsync(keys[0]).Result).Wait();
            if (stringIntMap[keys[0]] != -values[0])
                throw new Exception(String.Format("After setting one element many times, that element was not correct later!  Key: {0}  Expected: {1}  Got: {2}", keys[0], -values[0], stringIntMap[keys[0]]));

            List<int> unseen = new List<int>(values);
            foreach (var value in stringIntMap.GetValuesAsync(keys).Result)
            {
                if (unseen.Count == 0)
                    throw new Exception("GetValuesAsync returned more values than we expected to see in the list!");
                // Remember that we negated the values in the CloudStringDictionary.
                if (!unseen.Remove(-value))
                    throw new Exception(String.Format("The value {0} was either already seen or was unexpected.", value));
            }
            if (unseen.Count > 0)
                throw new Exception(String.Format("Calling GetValuesAsync did not show us all the values expected in the list.  We expected to see {0} more", unseen.Count));

            // Remove items one by one.
            for (int i = 0; i < num; i++)
            {
                if ((i % 2) == 0)
                {
                    if (!stringIntMap.Remove(keys[i]))
                        throw new Exception(String.Format("Remove failed for key {0}", keys[i]));
                }
                else
                {
                    if (!stringIntMap.RemoveAsync(keys[i]).Result)
                        throw new Exception(String.Format("RemoveAsync failed for key {0}", keys[i]));
                }
            }
            
            if (stringIntMap.Count != 0)
                throw new Exception(String.Format("After removing keys one by one, Count should have been 0, but got: {0}", stringIntMap.Count));

            // Should be redundant
            stringIntMap.Clear();

            if (stringIntMap.Count != 0)
                throw new Exception(String.Format("After calling Clear on an empty dictionary, Count should have been 0, but got: {0}", stringIntMap.Count));
            stringIntMap.Dispose();

            Console.WriteLine("CloudStringDictionary<int> test completed.");
        }

        public static void StringDictionaryTestWithCustomerObjects(String fabricAddress, bool useGateway)
        {
            Console.WriteLine("Starting CloudStringDictionary<Customer> test...");
            CloudStringDictionary<Customer> customers = new CloudStringDictionary<Customer>(fabricAddress, "Customers", useGateway);
            if (customers.Count > 0)
            {
                customers.Clear();
                if (customers.Count != 0)
                    throw new Exception(String.Format("After calling Clear on a dictionary, Count should have been 0, but got: {0}", customers.Count));
            }
            List<Customer> sampleCustomers = GetSampleCustomers();
            foreach (Customer customer in sampleCustomers)
                customers[customer.FirstName] = customer;
            if (customers.Count != sampleCustomers.Count)
                throw new Exception(String.Format("After adding customers to a CloudStringDictionary<Customer>, count wasn't the expected value.  Duplicate customers, or a bug?  Expected: {0}  Got: {1}", sampleCustomers.Count, customers.Count));

            // Test lookup
            Console.WriteLine("Testing lookup");
            foreach (Customer c in sampleCustomers)
            {
                if (!c.Equals(customers[c.FirstName]))
                    throw new Exception(String.Format("Looking up a customer named {0} failed to return the right customer.  Got: {1}", c.FirstName, customers[c.FirstName]));
            }

            // Test CopyTo
            Console.WriteLine("Testing CopyTo");
            KeyValuePair<String, Customer>[] pairs = new KeyValuePair<String, Customer>[customers.Count + 1];
            customers.CopyTo(pairs, 1);
            if (pairs[0].Key != null || pairs[0].Value != null)
                throw new Exception("After calling CopyTo, the first element in our array was overwritten!");
            for (int i = 1; i < customers.Count + 1; i++)
                if (pairs[i].Key != pairs[i].Value.FirstName)
                    throw new Exception(String.Format("CopyTo returned a pair that looked inconsistent!  Key: {0}  Value: {1}", pairs[i].Key, pairs[i].Value));
            Array.Clear(pairs, 0, pairs.Length);

            customers.CopyToAsync(pairs, 1).Wait();
            if (pairs[0].Key != null || pairs[0].Value != null)
                throw new Exception("After calling CopyToAsync, the first element in our array was overwritten!");
            for (int i = 1; i < customers.Count + 1; i++)
                if (pairs[i].Key != pairs[i].Value.FirstName)
                    throw new Exception(String.Format("CopyToAsync returned a pair that looked inconsistent!  Key: {0}  Value: {1}", pairs[i].Key, pairs[i].Value));


            // Check non-existent values
            if (customers.ContainsKey("non-existent"))
                throw new Exception("ContainsKey on a non-existent key returned true!");
            if (customers.ContainsKeyAsync("non-existent").Result)
                throw new Exception("ContainsKeyAsync on a non-existent key returned true!");
            Customer outValue;
            if (customers.TryGetValue("non-existent", out outValue))
                throw new Exception("TryGetValue on a non-existent key returned true!");
            if (customers.TryGetValueAsync("non-existent").Result.Item1)
                throw new Exception("TryGetValueAsync on a non-existent key returned true!");
            if (customers.Remove("non-existent"))
                throw new Exception("Remove on a non-existent key returned true!");
            if (customers.RemoveAsync("non-existent").Result)
                throw new Exception("RemoveAsync on a non-existent key returned true!");

            // Test the Keys collection
            Console.WriteLine("  Testing the Keys collection's enumerator...");
            List<String> unseenFirstNames = new List<String>(sampleCustomers.Select(c => c.FirstName));
            ICollection<String> customersKeys = customers.Keys;
            foreach(String firstName in customersKeys)
            {
                if (unseenFirstNames.Count == 0)
                    throw new Exception(String.Format("We've seen all customer first names when enumerating the keys, yet we were given one more first name while enumerating the Keys enumerator!  Duplicated or errant value: {0}", firstName));
                unseenFirstNames.Remove(firstName);

                if (!customersKeys.Contains(firstName))
                    throw new Exception(String.Format("While enumerating the dictionary's Keys collection, the Keys collection said it did not contain one of the items yielded by its enumerator!  Item: {0}", firstName));

                if (!customers.ContainsKey(firstName))
                    throw new Exception(String.Format("While enumerating the dictionary's Keys collection, the dictionary said it did not contain one of the keys yielded by the Keys enumerator when calling ContainsKey!  Item: {0}", firstName));
                if (!customers.ContainsKeyAsync(firstName).Result)
                    throw new Exception(String.Format("While enumerating the dictionary's Keys collection, the dictionary said it did not contain one of the keys yielded by the Keys enumerator when calling ContainKeyAsync!  Item: {0}", firstName));
            }
            if (unseenFirstNames.Count != 0)
                throw new Exception(String.Format("When enumerating through the keys, we did not see {0} customer first names.", unseenFirstNames.Count));


            // Test the Values collection
            Console.WriteLine("  Testing the Values collection's enumerator...");
            List<Customer> unseenCustomers = new List<Customer>(sampleCustomers);
            foreach (Customer customer in customers.Values)
            {
                if (unseenCustomers.Count == 0)
                    throw new Exception(String.Format("We've seen all customers when enumerating the values, yet we were given one more customer from our Values enumerator!  Duplicated or errant value: {0}", customer));
                unseenCustomers.Remove(customer);
            }
            if (unseenCustomers.Count != 0)
                throw new Exception(String.Format("When enumerating through the values, we did not see {0} customers.", unseenCustomers.Count));


            // Test the enumerator
            Console.WriteLine("  Testing the enumerator...");
            unseenFirstNames = new List<String>(sampleCustomers.Select(c => c.FirstName));
            foreach (var pair in customers)
            {
                if (unseenFirstNames.Count == 0)
                    throw new Exception(String.Format("We've seen all customer first names when enumerating the dictionary, yet we were given one more KeyValuePair while enumerating the dictionary!  Duplicated or errant key: {0}", pair.Key));
                if (pair.Key != pair.Value.FirstName)
                    throw new Exception(String.Format("Key/value pair from enumerator was screwed up.  Key should be value's first name field.  Key: {0}  Value's first name: {1}", pair.Key, pair.Value.FirstName));
                if (!sampleCustomers.Contains(pair.Value))
                    throw new Exception(String.Format("Enumerating the dictionary returned a Customer that wasn't in our initial set!  Key: {0}  Value.FirstName: {1}  Value.LastName: {2}", pair.Key, pair.Value.FirstName, pair.Value.LastName));
                if (!unseenFirstNames.Remove(pair.Key))
                    throw new Exception(String.Format("While enumerating dictionary, expected that any key we get back would be in our set of unseen first names, but it isn't!  Duplicated or errant key: {0}", pair.Key));

                if (!customers.Contains(pair))
                    throw new Exception(String.Format("While enumerating dictionary, we got a pair back, but when we call Contains on the dictionary for the pair, it returned false!  Key: {0}", pair.Key));
            }
            if (unseenFirstNames.Count != 0)
                throw new Exception(String.Format("When enumerating through the dictionary, we did not see {0} KeyValuePairs.", unseenFirstNames.Count));

            // Test the IEnumerator (non-generic) version of GetEnumerator
            Console.WriteLine("  Testing the non-generic enumerator...");
            foreach (KeyValuePair<String, Customer> pair in ((System.Collections.IEnumerable)customers))
            {
                if (pair.Key != pair.Value.FirstName)
                    throw new Exception(String.Format("Key/value pair from enumerator was screwed up.  Key should be value's first name field.  Key: {0}  Value's first name: {1}", pair.Key, pair.Value.FirstName));
                if (!sampleCustomers.Contains(pair.Value))
                    throw new Exception(String.Format("Enumerating the dictionary returned a Customer that wasn't in our initial set!  Key: {0}  Value.FirstName: {1}  Value.LastName: {2}", pair.Key, pair.Value.FirstName, pair.Value.LastName));                
            }


            // Remove one item, check Contains, etc.
            Console.WriteLine("Testing Remove and behavior with ContainsKey & TryGetValue");
            Customer testCustomer = sampleCustomers[0];
            if (!Object.Equals(customers.GetValueAsync(testCustomer.FirstName).Result, testCustomer))
                throw new Exception("GetValueAsync should have worked!");
            var maybeCustomer = customers.TryGetValueAsync(testCustomer.FirstName).Result;
            if (!maybeCustomer.Item1)
                throw new Exception("TryGetValueAsync returned false on an item that should have been in the dictionary.");

            if (!customers.Remove(testCustomer.FirstName))
                throw new Exception("Remove on a customer that is in the dictionary should have returned true!");

            if (customers.Remove(testCustomer.FirstName))
                throw new Exception("Second call to Remove on a customer that is no longer in the dictionary should have returned false!");
            if (customers.ContainsKey(testCustomer.FirstName))
                throw new Exception("After removing a customer, expected ContainsKey to return false for that customer's first name!");
            maybeCustomer = customers.TryGetValueAsync(testCustomer.FirstName).Result;
            if (maybeCustomer.Item1)
                throw new Exception("TryGetValueAsync returned true after we removed the item.");

            // RemoveAsync with a collection of keys
            // Remove three values, the first of which is already removed.
            String[] toRemove = new String[] { sampleCustomers[0].FirstName, sampleCustomers[1].FirstName, sampleCustomers[2].FirstName };
            customers.RemoveAsync(toRemove).Wait();
            foreach (String removeThis in toRemove)
            {
                Customer value;
                if (customers.TryGetValue(removeThis, out value))
                    throw new Exception(String.Format("Called RemoveAsync(IEnumerable<TKey>), but afterwards, TryGetValue found an item in the dictionary!  Key: {0}", removeThis));
                if (customers.ContainsKeyAsync(removeThis).Result)
                    throw new Exception(String.Format("Called RemoveAsync(IEnumerable<TKey>), but afterwards ContainsKeyAsync found an item in the dictionary!  Key: {0}", removeThis));
            }
            if (customers.CountAsync.Result != sampleCustomers.Count - 3)
                throw new Exception(String.Format("After calling RemoveAsync(IEnumerable<TKey>), expected count to be {0} but it was {1}", sampleCustomers.Count - 3, customers.CountAsync.Result));

            customers.ClearAsync().Wait();
            if (customers.CountAsync.Result != 0)
                throw new Exception(String.Format("After calling Clear on a dictionary, Count should have been 0, but got: {0}", customers.Count));

            customers.Destroy();
            customers.Dispose();

            Console.WriteLine("CloudStringDictionary<Customer> test completed.");
        }

        #endregion CloudStringDictionary Tests

        private static List<Customer> GetSampleCustomers()
        {
            List<Customer> customers = new List<Customer>(new[] {
                new Customer("Erik", "Meijer", "Lunar Base", "The Moon"),
                new Customer("Brian", "Grunkemeyer", "Redmond", "USA"),
                new Customer("Joe", "Hoag", "Berlin", "Germany"),
                new Customer("Donna", "Malayeri", "Tokyo", "Japan"),
                new Customer("Georgi", "Chkodrov", "Moscow", "Russia"),
                new Customer("Test", "de Customer", "Redmond", "USA"),
            });
            return customers;
        }

        private static List<List<Order>> GetSampleOrderLists()
        {
            var orderMap = new List<List<Order>>();
            orderMap.Add(new List<Order>(new[] { new Order("Unicorn meat", 25), new Order("Shaved bacon", 2) }));
            orderMap.Add(new List<Order>(new[] { new Order("Tesla Roadster", 1), new Order("Macallan 25 year", 3) }));
            orderMap.Add(new List<Order>(new[] { new Order("Non-alcoholic lemonade", 5), new Order("Peace & quiet", 10) }));
            orderMap.Add(new List<Order>(new[] { new Order("Purple rubber duck", 100), new Order("Ticket to Pittsburgh", 1) }));
            orderMap.Add(null);
            orderMap.Add(new List<Order>(0));
            return orderMap;
        }

        #region CloudDictionary tests

        public static void CloudDictionaryIntFloatTest(String fabricAddress, bool useGateway)
        {
            Console.WriteLine("Testing CloudDictionary<Int32, Single>");

            CloudDictionary<Int32, float> intFloatMap = new CloudDictionary<int, float>(fabricAddress, "IntFloatMap", connectThroughGateway:useGateway);
            if (intFloatMap.Count != 0)
                intFloatMap.Clear();
            int num = 5;
            for (int i = 0; i < num; i++)
            {
                intFloatMap.Add(i, 0.1f + i);
            }
            if (intFloatMap.Count != num)
                throw new Exception("Count returned the wrong value!");
            if (intFloatMap.CountAsync.Result != num)
                throw new Exception("CountAsync returned the wrong value!");

            // Lookup
            Console.WriteLine("  Testing lookup");
            ICollection<int> keyCollection = intFloatMap.Keys;
            ICollection<float> valueCollection = intFloatMap.Values;
			var sortedValues = new List<float>(valueCollection);
			sortedValues.Sort();
            for (int i = 0; i < num; i++)
            {
                float value, expected = 0.1f + i;
                value = intFloatMap[i];
                if (!IsApproximatelyEqual(value, expected))
                    throw new Exception(String.Format("Lookup using the indexer for {0} returned the wrong value.  Got: {1}", i, value));
                if (intFloatMap.TryGetValue(i, out value))
                {
                    if (!IsApproximatelyEqual(value, expected))
                        throw new Exception(String.Format("tryGetValue returned a value, but it was the wrong value.  Index: {0}  Got: {1}  Expected: {2}", i, value, expected));
                }
                else
                    throw new Exception("TryGetValue failed to find an item that should be in the dictionary.");
                if (!intFloatMap.ContainsKeyAsync(i).Result)
                    throw new Exception(String.Format("ContainsKeyAsync returned false for an item that should be in the collection!  Index: {0}", i));
                if (!intFloatMap.ContainsKey(i))
                    throw new Exception(String.Format("ContainsKey returned false for an item that should be in the collection!  Index: {0}", i));

                if (!keyCollection.Contains(i))
                    throw new Exception(String.Format("Keys collection's Contains method returned false for {0}", i));

                // Contains on a floating point value is a bad idea because equality on floating point numbers isn't reliable.
                // But, we can still ensure there's an approximately equal floating point number in the values collection, because we can sort it.
				if (!IsApproximatelyEqual(sortedValues[i], expected))
				    throw new Exception(String.Format("Values collection didn't appear to contain a value for item {0}.  Expected: {1}  Got: {2}", i, expected, sortedValues[i]));
            }

            // Enumerate
            Console.WriteLine("  Testing enumerator");
            bool[] seen = new bool[num];
            foreach (KeyValuePair<int, float> pair in intFloatMap)
            {
                if (pair.Key < 0 || pair.Key >= num)
                    throw new Exception(String.Format("Enumerate returned an unexpected key {0}", pair.Key));
                if (seen[pair.Key])
                    throw new Exception(String.Format("Enumerate returned a duplicated key!  Key: {0}", pair.Key));
                seen[pair.Key] = true;
                if (!IsApproximatelyEqual(pair.Value, 0.1f + pair.Key))
                    throw new Exception(String.Format("Enumerate returned the wrong value for a key.  Key: {0}  Value: {1}", pair.Key, pair.Value));
            }
            for (int i = 0; i < num; i++)
                if (!seen[i])
                    throw new Exception(String.Format("Enumerate skipped an item!  Missed key: {0}", i));

            // KeyCollection
            Console.WriteLine("  Enumerating key collection");
            if (keyCollection.Count != num)
                throw new Exception(String.Format("Key collection count is incorrect.  Got: {0}", keyCollection.Count));
            Array.Clear(seen, 0, num);
            foreach (int key in keyCollection)
            {
                if (seen[key])
                    throw new Exception(String.Format("Enumerating KeysCollection returned a duplicated key!  Key: {0}", key));
                seen[key] = true;
            }
            for (int i = 0; i < num; i++)
                if (!seen[i])
                    throw new Exception(String.Format("Enumerating the Keys collection skipped an item!  Missed key: {0}", i));

            // ValueCollection
            Console.WriteLine("  Enumerating value collection");
            if (valueCollection.Count != num)
                throw new Exception(String.Format("Value collection count is incorrect.  Got: {0}", valueCollection.Count));
            float f = 0.1f;
            Array.Clear(seen, 0, num);
            foreach (float value in valueCollection)
            {
                int key = (int)Math.Truncate(value);
                //Console.WriteLine("    Value: {0}  Converting float back to an int for the key: {1}", value, key);
                if (seen[key])
                    throw new Exception(String.Format("Enumerating ValueCollection returned a duplicated key!  Key: {0}", key));
                seen[key] = true;
            }
            for (int i = 0; i < num; i++)
                if (!seen[i])
                    throw new Exception(String.Format("Enumerating the Values collection skipped an item!  Missed value's key: {0}", i));

            Console.WriteLine("Ensuring RemoveAsync, TryGetValue, and TryGetValueAsync work.");
            intFloatMap.RemoveAsync(1).Wait();
            if (intFloatMap.Count != num - 1)
                throw new Exception("After RemoveAsync, Count returned the wrong value!");
            if (intFloatMap.ContainsKeyAsync(1).Result)
                throw new Exception("After RemoveAsync, ContainsKeyAsync returned true!");
            if (intFloatMap.ContainsKey(1))
                throw new Exception("After RemoveAsync, ContainsKey returned true!");
            if (intFloatMap.TryGetValueAsync(1).Result.Item1)
                throw new Exception("After RemoveAsync, TryGetValueAsync returned true!");
            if (intFloatMap.TryGetValue(1, out f))
                throw new Exception("After RemoveAsync, TryGetValue returned true!");
            if (intFloatMap.Keys.Contains(1))
                throw new Exception("After RemoveAsync, new Keys Collection's Contains returned true!");

            // Now try re-adding the item
            intFloatMap.SetValueAsync(1, -1.1f).Wait();
            if (!intFloatMap.TryGetValue(1, out f))
                throw new Exception("After Removing then re-setting a value, TryGetValue returned true!");
            if (!IsApproximatelyEqual(f, -1.1f))
                throw new Exception("After removing then re-setting a value, TryGetValue set the wrong out value!");
            Tuple<bool, float> retVal;
            if (!(retVal = intFloatMap.TryGetValueAsync(1).Result).Item1)
                throw new Exception("After Removing then re-setting a value, TryGetValueAsync returned true!");
            if (!IsApproximatelyEqual(retVal.Item2, -1.1f))
                throw new Exception("After Removing then re-setting a value, TryGetValueAsync returned the wrong floating point value!");


            intFloatMap.ClearAsync().Wait();
            if (intFloatMap.CountAsync.Result != 0)
                throw new Exception("After calling ClearAsync, CountAsync didn't return 0!");
            intFloatMap.Dispose();

            Console.WriteLine("Finished testing CloudDictionary<Int32, Single>");
        }

        // This test is more focused on ensuring the appropriate breadth of values can be represented and deserialized,
        // due to our reliance on JSON serialization which has only one Number type.
        public static void CloudDictionaryNumericsTest(String fabricAddress, bool useGateway)
        {
            Console.WriteLine("CloudDictionary Numerics tests");

            Console.WriteLine("  Byte -> UInt64");
            CloudDictionary<byte, UInt64> byteULongMap = new CloudDictionary<byte, ulong>(fabricAddress, "Byte to UInt64", connectThroughGateway:useGateway);
            if (byteULongMap.Count > 0)
                byteULongMap.Clear();
            ulong highValue = 1ul << 51;  // Javascript should support up to 2^53 as integers
            byteULongMap[0] = highValue;
            byteULongMap[255] = (ulong)0;
            ulong midValue = ((UInt64)UInt32.MaxValue + 2);  // 0x100000001
            byteULongMap[5] = midValue;
            byteULongMap[1] = 2;
            if (byteULongMap[255] != 0)
                throw new Exception(String.Format("Looking up the key 255 returned the wrong value.  Got: {0}", byteULongMap[255]));
            if (byteULongMap[1] != 2)
                throw new Exception(String.Format("Looking up the key 1 returned the wrong value.  Got: {0}", byteULongMap[1]));
            if (byteULongMap[5] != midValue)
                throw new Exception(String.Format("Looking up the key 5 returned the wrong value.  Got: {0}", byteULongMap[5]));
            if (byteULongMap[0] != highValue)
                throw new Exception(String.Format("Looking up the key 0 returned the wrong value.  Got: {0}", byteULongMap[0]));
            byteULongMap.Destroy();

            Console.WriteLine("  UInt32 -> SByte");
            CloudDictionary<UInt32, SByte> uintSByteMap = new CloudDictionary<uint, sbyte>(fabricAddress, "UInt32 to SByte", connectThroughGateway:useGateway);
            if (uintSByteMap.Count > 0)
                uintSByteMap.Clear();
            uintSByteMap[1] = 2;
            uintSByteMap[UInt32.MaxValue] = SByte.MinValue;
            if (uintSByteMap[1] != 2)
                throw new Exception(String.Format("In uintSByteMap, looking up the key 1 returned the wrong value.  Got: {0}", uintSByteMap[1]));
            if (uintSByteMap[UInt32.MaxValue] != SByte.MinValue)
                throw new Exception(String.Format("In uintSByteMap, looking up the key UInt32.MaxValue returned the wrong value.  Got: {0}", uintSByteMap[UInt32.MaxValue]));
            uintSByteMap.Destroy();

            Console.WriteLine("CloudDictionary Numerics tests completed");
        }

        public static void CloudDictionaryCustomerOrderTest(String fabricAddress, bool useGateway)
        {
            Console.WriteLine("CloudDictionary<Customer, List<Order>> test");

            CloudDictionary<Customer, List<Order>> orderMap = new CloudDictionary<Customer, List<Order>>(fabricAddress, "Customer Orders", connectThroughGateway:useGateway);
            if (orderMap.Count > 0)
                orderMap.Clear();

            List<Customer> customers = GetSampleCustomers();
            List<List<Order>> orders = GetSampleOrderLists();
            Contract.Assert(customers.Count == orders.Count);
            Task task = orderMap.SetValuesAsync(customers, orders);
            task.Wait();

            for (int i = 0; i < customers.Count; i++)
            {
                if (!orderMap.ContainsKey(customers[i]))
                    throw new Exception(String.Format("ContainsKey for customer {0} returned false!", customers[i]));
                if (!orderMap.ContainsKeyAsync(customers[i]).Result)
                    throw new Exception(String.Format("ContainsKeyAsync for customer {0} returned false!", customers[i]));

                List<Order> retrievedOrders = orderMap.GetValueAsync(customers[i]).Result;
                CompareLists(orders[i], retrievedOrders, "GetValueAsync");

                retrievedOrders = orderMap[customers[i]];
                CompareLists(orders[i], retrievedOrders, "CloudDictionary default indexed property");

                if (!orderMap.TryGetValue(customers[i], out retrievedOrders))
                    throw new Exception(String.Format("TryGetValue for customer {0} failed!", customers[i]));
                CompareLists(orders[i], retrievedOrders, "TryGetValue");

                var tuple = orderMap.TryGetValueAsync(customers[i]).Result;
                if (!tuple.Item1)
                    throw new Exception(String.Format("TryGetValue for customer {0} failed!", customers[i]));
                retrievedOrders = tuple.Item2;
                CompareLists(orders[i], retrievedOrders, "TryGetValueAsync");

                Console.WriteLine("  {0} {1} ordered the following items to {2}", customers[i].FirstName, customers[i].LastName, customers[i].City);
                if (retrievedOrders == null || retrievedOrders.Count == 0)
                    Console.WriteLine("    nothing");
                else
                {
                    for (int j = 0; j < retrievedOrders.Count; j++)
                        Console.WriteLine("    {0}x {1}", retrievedOrders[j].Quantity, retrievedOrders[j].Item);
                }
            }

            // Test setting null for a value
            orderMap[customers[0]] = null;
            if (orderMap[customers[0]] != null)
                throw new Exception("After setting a value to null, we didn't get null back when retrieving that value!");

            if (orderMap.CountAsync.Result > 0)
                orderMap.ClearAsync().Wait();
            orderMap.Dispose();
            Console.WriteLine("Done with CloudDictionary<Customer, List<Order>> tests");
        }

        public static void CloudDictionaryPartitionedTest(String fabricAddress, bool useGateway)
        {
            Console.WriteLine("Partitioned CloudDictionary<int, String> test");

            int numPartitions = 5;
            int numDictionaries = 75;
            Console.WriteLine("Creating {0} dictionaries on {1} partitions, inserting 0 - {0} items to them & verifying.", numDictionaries, numPartitions);
            Stopwatch sw = Stopwatch.StartNew();

            for(int i=0; i<numDictionaries; i++)
            {
                var dict = new CloudDictionary<int, string>(fabricAddress, "PartitionedDictionaryTest", "dict " + i, numPartitions, connectThroughGateway:useGateway);
                dict.Clear();
                for (int j = i; j < numDictionaries; j++)
                    dict.Add(j, j.ToString());
                if (dict.Count != numDictionaries - i)
                    throw new Exception(String.Format("After adding numReps - i items to the dictionary, expected to find that many there!  Expected: {0}  Dictionary count: {1}",
                                                      numDictionaries - 1, dict.Count));
                for(int j = i; j < numDictionaries; j++) {
                    String got = dict[j];
                    if (!String.Equals(got, j.ToString()))
                        throw new Exception(String.Format("Got wrong value from CloudDictionary<int, String>!  Dictionary number {0}  Int for index: {1}  Got: {2}", i, j, got));
                }
                dict.Clear();
                //dict.Destroy();
                dict.Dispose();
            }
            sw.Stop();
            Console.WriteLine("Created {0} dictionaries & populated them in {1:0.0} seconds  {2:0} ms/dictionary", 
			                  numDictionaries, sw.Elapsed.TotalSeconds, sw.ElapsedMilliseconds / numDictionaries);
            Console.WriteLine("Done with Partitioned CloudDictionary test");
        }

        #endregion CloudDictionary tests

        #region Support methods

        private static void CompareLists(List<Order> expected, List<Order> found, String sourceOfValue)
        {
            if (found == null)
            {
                if (expected == null)
                    return;
                else
                    throw new Exception("Unexpectedly got a null list!");
            }

            if (expected.Count != found.Count)
                throw new Exception(String.Format("List counts aren't equal - Expected: {0}  Got: {1}  Source: {2}", expected.Count, found.Count, sourceOfValue));
            for (int i = 0; i < expected.Count; i++)
            {
                if (!expected[i].Equals(found[i]))
                    throw new Exception(String.Format("List contained the wrong value.  Index: {0}  Expected value: {1}  Got: {2}  Source: {3}", i, expected[i], found[i], sourceOfValue));
            }
        }

        // As you know, floats are 32 bits and Intel processors use 80 bits internally.  It's easy to get values that aren't
        // exactly identical when comparing the result of a calculation with contents read from a 32 bit register, etc.  
        // Vance points out that floating point numbers are just sloppy like this by design.  You can easily get different
        // results from debug vs. retail code, or really subtle number problems.  Use this instead of ==.
        private static bool IsApproximatelyEqual(float x, float y)
        {
            float threshold = 0.001f;  // .1%
            if (x == 0.0f)
            {
                return y > -threshold && y < threshold;
            }
            else
            {
                float diff = x - y;
                return Math.Abs(diff) / x < threshold;
            }
        }

        #endregion Support methods
    }
}
